//
// Created by Guoshun WU on 2021/2/20.
//

#include "WGSMPlayer.h"
#include "Log.h"
#include <SDL_audio.h>
#include <opusfile.h>

#include <thread>

using namespace std;

int simplePlayerTest() {
    if (0 != SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_AUDIO)) {
        logError("SDL init error: %s", SDL_GetError());
        return 1;
    }


    auto win = SDL_CreateWindow("Hello world", 100, 100, 640, 480, SDL_WINDOW_SHOWN);
    if (nullptr == win) {
        logError("SDL create window error: %s", SDL_GetError());

        SDL_Quit();
        return 1;
    }

    int count = SDL_GetNumAudioDevices(0);

    for (int i = 0; i < count; ++i) {
        printf("Audio device %d: %s\n", i, SDL_GetAudioDeviceName(i, 0));
    }

    int err;
    auto opusFile = op_open_file("../resources/ogg/txh.opus", &err);

    logInfo("is seekable: %d", op_seekable(opusFile));
    logInfo("link count: %d", op_link_count(opusFile));
    auto currentLink = op_current_link(opusFile);
    auto serial = op_serialno(opusFile, currentLink);
    logInfo("serial: %x, current link: %d", serial, currentLink);
    auto channel = op_channel_count(opusFile, currentLink);
    logInfo("channel: %d", channel);
    auto rawTotal = op_raw_total(opusFile, currentLink);
    logInfo("raw total: %ld", (long) rawTotal);
    auto pcmTotal = op_pcm_total(opusFile, currentLink);
    logInfo("pcm total: %ld", (long) pcmTotal);
    logInfo("bit rate: %d", op_bitrate(opusFile, currentLink));

    const int OUTPUT_SAMPLE_RATE = 48000;
    // http://wiki.libsdl.org/SDL_AudioSpec
    struct CallbackParameter {
        OggOpusFile *file;
        SDL_AudioDeviceID dev;
    } callbackParam{
            opusFile,
            0
    };

    SDL_AudioDeviceID dev;
    SDL_AudioSpec desired{
            OUTPUT_SAMPLE_RATE,
            AUDIO_S16LSB,
            static_cast<Uint8>(channel),
            0,
            OUTPUT_SAMPLE_RATE / 1000 * 120,
            0,
            0,
            [](void *userdata, Uint8 *stream,
               int len) -> void {
                auto param = static_cast<CallbackParameter *>(userdata);
                auto opusFile = param->file;
                auto currentLink = op_current_link(opusFile);

                auto bufBegin = stream;
                auto bufEnd = stream + len;
                while (bufBegin < bufEnd) {
                    auto numOfSamples = op_read(opusFile, reinterpret_cast<opus_int16 *>(bufBegin),
                                                static_cast<opus_int16 >(bufEnd - bufBegin) / 2,
                                                &currentLink);
                    bufBegin += numOfSamples * 2;
                }
                // the buffer size need to be match with this filled buffer
                auto pcmPos = op_pcm_tell(opusFile);
                auto bitrate = op_bitrate_instant(opusFile);
//                auto rawTotal = op_raw_total(opusFile, currentLink);
                auto pcmTotal = op_pcm_total(opusFile, currentLink);
                logInfo("Need %d length data, read %d samples, progress = %.1f%%, bitrate =%.1f kbps, thread_id=%ld",
                        len, len / 2, ((double) pcmPos) * 100 / pcmTotal, bitrate / 1000.0f,
                        get_current_thread_id());
                if (pcmPos >= pcmTotal) {
                    // this would cause CPU very busy
//                    SDL_PauseAudioDevice(param->dev, 1);
                }
            },
            &callbackParam,
    };
    SDL_AudioSpec obtained;
    dev = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_FORMAT_CHANGE);
    callbackParam.dev = dev;

    if (0 == dev) {
        SDL_Log("Failed to open audio: %s", SDL_GetError());
    } else {
        if (desired.format != obtained.format) {
            SDL_Log("We didn't get s16le audio.");
        }
    }

    SDL_PauseAudioDevice(dev, 0);

    //  It is recommended that this be large enough for at least 120 ms of data at 48 kHz per channel (5760 values per channel).
//    int duration = 120; //ms
//    int bufSize = (OUTPUT_SAMPLE_RATE / 1000) * duration * channel;
    // the bufSize is the frame size, 5760 per channel
//    opus_int16 buf[bufSize];

    // using SDL_QueueAudio
#if 0
    while (true) {
        auto numOfSamples = op_read(opusFile, buf, bufSize, &currentLink);
        if (numOfSamples < 0) { // error handle
            logWarn("decode pcm failed: %d", numOfSamples);
        } else if (0 == numOfSamples) {
            logInfo("End of file reached or buffer is to small to store even one sample.");
            break;
        } else {
            // write pcm buffer to output
            // op_head(opusFile, currentLink);
            if (SDL_QueueAudio(dev, buf, numOfSamples * 2) < 0) {
                SDL_Log("Queued %d samples failed: %s", numOfSamples, SDL_GetError());
            }
            auto pcmPos = op_pcm_tell(opusFile);
            auto bitrate = op_bitrate_instant(opusFile);

            // sample rate is 48000, so 48 samples for one milliseconds
            auto sampleDuration = numOfSamples / (48 * channel);
            logDebug("Write %d sample, percentage=%.2lf%%, bitrate=%.1lf kbps, read sample duration=%d ms.",
                     numOfSamples, ((double) pcmPos) * 100 / pcmTotal, bitrate / 1000.0, sampleDuration);
//            auto now = chrono::high_resolution_clock::now();
//            std::chrono::duration<double, std::milli> elapsed = now - lastFrameGotTime;
//            auto needSleepSec = sampleDuration - elapsed;
//             sleep duration to match the speed or open Alsa playback in block mode
//            this_thread::sleep_for(chrono::milliseconds(sampleDuration));
//            lastFrameGotTime = now;
        }
    }
#endif

    SDL_Event e;
    bool quit = false;
    while (!quit) {
        logDebug("Outlooper.......");
//        while (SDL_PollEvent(&e)) {
        while (SDL_WaitEvent(&e)) {
            switch (e.type) {
                case SDL_QUIT:
                case SDL_KEYDOWN:
                case SDL_MOUSEBUTTONDOWN:
                    quit = true;
                    break;
            }
        }
    }

    SDL_CloseAudioDevice(dev);
    op_free(opusFile);

    SDL_DestroyWindow(win);
    SDL_Quit();
    return 0;
}

int testWGSMPlayer(int argc, char *argv[]) {
    int ret = -1;
//    WGSMPlayer player;
//    player.playOggOpusFile("../resources/ogg/txh.opus");
//    player.startSDLEventLoop();
//    ret = simplePlayerTest();
//    logDebug("current thread id: %ld", this_thread::get_id());
    logDebug("Hello, test player...");
    return ret;
}

WGSMPlayer::WGSMPlayer() {
    if (0 != SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_AUDIO)) {
        logError("SDL init error: %s", SDL_GetError());
    }
}

int WGSMPlayer::playOggOpusFile(const char *file) {

    if (nullptr != currentPlayingFile && 0 == strcmp(file, currentPlayingFile)) {
        logInfo("File %s is playing now.", file);
        return 0;
    }
    logInfo("Playing file %s", file);
    int err;
    auto opusFile = op_open_file(file, &err);
    if (nullptr == opusFile) {
        logError("Open opus file error: %s", opus_strerror(err));
        return -1;
    }

    currentPlayingFile = file;

    logInfo("is seekable: %d", op_seekable(opusFile));
    logInfo("link count: %d", op_link_count(opusFile));
    auto currentLink = op_current_link(opusFile);
    auto serial = op_serialno(opusFile, currentLink);
    logInfo("serial: %x, current link: %d", serial, currentLink);
    auto channel = op_channel_count(opusFile, currentLink);
    logInfo("channel: %d", channel);
    auto rawTotal = op_raw_total(opusFile, currentLink);
    logInfo("raw total: %ld", (long) rawTotal);
    auto pcmTotal = op_pcm_total(opusFile, currentLink);
    logInfo("pcm total: %ld", (long) pcmTotal);
    logInfo("bit rate: %d", op_bitrate(opusFile, currentLink));

    const int OUTPUT_SAMPLE_RATE = 48000;
    currentCallbackParams.file = opusFile;
    SDL_AudioDeviceID dev;

    uint16_t callbackBufSize = OUTPUT_SAMPLE_RATE / 1000 * 120;
    SDL_AudioSpec desired{
            OUTPUT_SAMPLE_RATE,
            AUDIO_S16LSB,
            static_cast<Uint8>(channel),
            0,
            callbackBufSize,
            0,
            0,
            &WGSMPlayer::opusFileFeedCallBack,
            &currentCallbackParams,
    };
    SDL_AudioSpec obtained;
    // initialize SDL Audio sub system

    dev = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_FORMAT_CHANGE);
    currentCallbackParams.dev = dev;

    if (0 == dev) {
        SDL_Log("Failed to open audio: %s", SDL_GetError());
    } else {
        if (desired.format != obtained.format) {
            SDL_Log("We didn't get s16le audio.");
        }
    }

    // start event loop

    return 0;
}

/**
 * */
void WGSMPlayer::opusFileFeedCallBack(void *userdata, unsigned char *stream, int len) {
    auto param = static_cast<CallbackParameter *>(userdata);
    auto opusFile = param->file;
    auto currentLink = op_current_link(opusFile);

    auto bufBegin = stream;
    auto bufEnd = stream + len;
    while (bufBegin < bufEnd) {
        auto numOfSamples = op_read(opusFile, reinterpret_cast<opus_int16 *>(bufBegin),
                                    static_cast<opus_int16 >(bufEnd - bufBegin) / 2,
                                    &currentLink);
        bufBegin += numOfSamples * 2;
    }
    // the buffer size need to be match with this filled buffer
    auto pcmPos = op_pcm_tell(opusFile);
    auto bitrate = op_bitrate_instant(opusFile);
//                auto rawTotal = op_raw_total(opusFile, currentLink);
    auto pcmTotal = op_pcm_total(opusFile, currentLink);
    logInfo("Need %d length data, read %d samples, progress = %.1f%%, bitrate =%.1f kbps",
            len, len / 2, ((double) pcmPos) * 100 / pcmTotal, bitrate / 1000.0f);
    if (pcmPos >= pcmTotal) {
        // this would cause CPU very busy
//                    SDL_PauseAudioDevice(param->dev, 1);
    }
}

int WGSMPlayer::stopPlaying() {
    if (nullptr == currentCallbackParams.file) {
        logWarn("No file is been playing now.");
        return -1;
    }
    SDL_CloseAudioDevice(currentCallbackParams.dev);
    op_free(currentCallbackParams.file);


    return 0;
}

int WGSMPlayer::startSDLEventLoop() {

    SDL_Event e;
    bool quit = false;
    while (!quit && SDL_WaitEvent(&e)) {
        switch (e.type) {
            case SDL_QUIT:
                quit = true;
                break;
            case SDL_KEYDOWN: {
                // any key start play
            }
                break;
            case SDL_MOUSEBUTTONDOWN:

                break;
        }
    }

    return 0;
}
